﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

// 委托和事件
namespace Lesson12
{
    #region 委托
    // 什么是委托   委托是一个类型安全的函数指针
    // 委托是 函数的容器  可以理解为表示函数的变量类型  用来存储、传递函数
    // 委托的本质是一个类， 用来定义函数（方法）的类型 （返回值和参数的类型）
    // 不同的 函数（方法）必须对应和各自“格式”一致的委托

    // 基本语法
    // 关键字 delegate
    // 语法： 访问修饰符 delegate 返回值类型 委托名称(参数列表)
    // 声明再namespace或class语句块中，更多的写在 namespace 中

    // 定义自定义委托
    // 访问修饰符默认不写，为public 在别的命名空间中也能使用
    // 用private修饰 其它命名空间就不能使用了
    // 一般使用 public

    // 1. 声明一个委托
    delegate void MyDelegate();  // 定义了一个规则，没有使用
    // 用来 装载或传递 返回值为int 有一个int参数的函数 的委托
    delegate int MyDelegate2(int a); // 名字不能重名（同一语句块中）

    // 2. 使用定义好的委托
    // 委托变量是函数的容器
    // 委托常用在 ①作为类的成员  ②作为函数的参数
    class Test
    {
        // 在类中声明委托变量
        public MyDelegate fun;
        public MyDelegate2 fun2;

        public void TestFun()
        {
            // 可以先使用委托做一些操作
            fun.Invoke();
            fun2.Invoke(100);
        }

        public void TestFun2(MyDelegate d1, MyDelegate2 d2)
        {
            // 先处理一些别的逻辑，处理完了再执行传入的函数
            int i = 1;
            i++;
            i *= 2;

            //d1();
            //d2(i);

            this.fun = d1; 
            this.fun2 = d2; 
        }

        // 多播委托 增
        public void AddFun(MyDelegate d1, MyDelegate2 d2)
        {
            this.fun += d1; // 往委托中增加函数
            this.fun2 += d2; // 往委托中增加函数
        }
        // 删
        public void RemoveFun(MyDelegate d1, MyDelegate2 d2)
        {
            this.fun -= d1; // 往委托中删除函数
            this.fun2 -= d2; // 往委托中删除函数
        }
    }

    // 委托变量可以存储多个函数 （多播委托）

    // 系统定义好的委托
    // Action 在 System命名空间中
    // Action就是无参无返回的委托  直接使用
    // Func<>泛型委托 是指定返回类型的委托

    // Action<...> 泛型委托，是指定参数个数的委托，最多16个  但无返回值
    // Func<...> 泛型委托 可以指定多个参数，最多16个  但有返回值，返回值在最后一个，泛型括号中一共可以有17个参数

    // 自定义泛型委托
    delegate T MyFunc<T, K>(T t, K k);

    #endregion

    #region 事件
    // 什么是事件
    // 事件是委托的特殊用法
    // 事件是基于委托的存在
    // 事件是委托的安全包裹, 让委托的使用更安全
    // 事件 是一种特殊的变量类型

    // 事件的使用
    // 语法：访问修饰符 event 委托类型 事件名称;
    // 事件的使用
    //  事件是作为 成员变量存在于类中
    //  事件的使用和委托一样，委托怎么用，事件就怎么用
    // 事件和委托的区别
    //  1. 事件 不能再类外部 赋值
    //  2. 事件 只能在类外部 调用
    // 注意：它只能作为成员存在于类和接口以及结构体中

    class TestEvent
    {
        // 声明一个委托成员变量
        public Action myAction;
        // 声明一个事件
        public event Action myEvent; 

        public TestEvent()
        {
            // 事件的使用和委托基本一样   只是有一些限制
            myAction = TestFun; // 赋值
            myAction += TestFun; // 追加
            myAction -= TestFun; // 删除
            myAction(); // 调用
            myAction.Invoke(); // 调用
            myAction = null; // 清空

            myEvent = TestFun;
            myEvent += TestFun; // 追加
            myEvent -= TestFun; // 删除
            myEvent(); // 调用
            myEvent.Invoke(); // 调用
            myEvent = null; // 清空
        }

        // 触发事件
        public void TriggerEvent()
        {
            //if(myEvent != null)
            //{
            //    myEvent.Invoke();
            //}
            myEvent?.Invoke(); // ? 判空操作符
        }

        public void TestFun()
        {
            Console.WriteLine("TestFun");
        }
    }

    // 事件的作用
    // 防止外部随意置空委托
    // 防止外部随意调用委托
    // 事件是对委托的封装

    #endregion

    #region 匿名函数
    // 匿名函数就是没有名字的函数
    // 匿名函数主要是配合委托和事件进行使用
    // 脱离委托和事件 是不会使用匿名函数的
    // 匿名函数的使用
    // 语法
    // delegate(参数列表) { 函数体 // 函数逻辑 };
    // 何时使用：1.函数中传递委托参数时  2.委托或事件赋值时
    class TestAnonymous
    {
        
        public Action myAction;
        
        // 作为参数传递时
        public void DoSomething(int a, Action fun)
        {
            Console.WriteLine($"Something {a}");
            fun(); // 调用传入的函数
        }

        // 作为返回值
        public Action GetFun()
        {
            return delegate() {
                Console.WriteLine("类内部返回的匿名函数");
            };
        }
    }

    // 匿名函数的缺点
    // 添加到委托或事件中后，不记录 无法单独移除
    #endregion

    #region lambda表达式
    // lambda表达式可以理解为 匿名函数的简写
    // 除了写法不同，使用上和匿名函数一样。都是与委托和事件配合使用

    // lambda表达式的语法
    //  (参数列表) => { // 函数体 };

    // 闭包
    // 内层的函数可以引用包含在它外层的函数的变量，即使外层函数的执行已经终止
    // 注意： 该变量提供的值并非变量创建时的值，而是在父函数范围内的最终值
    class TestClosure
    {
        // 声明一个事件
        public event Action myEvent;
        public TestClosure()
        {
            int a = 10;

            myEvent = () =>
            {
                Console.WriteLine("闭包的值：" + a);  // 这里访问的a不会被释放，除非置空myEvent
            };

            for (int i = 0; i < 5; i++)
            {
                // 内部再定义一个临时变量
                int temp = i; 
                myEvent += () =>
                {
                    Console.WriteLine(i);     // 这里一直是5
                    Console.WriteLine(temp);     // 这里会逐步打印0-4
                };
            }
        }

        public void DoSomething()
        {
            myEvent(); // 触发事件
        }
    }

    #endregion

    internal class Program
    {
        static void Main(string[] args)
        {
            // 使用委托
            // 专门用来装载函数的容器，先定义一个函数
            // 实例化一个委托对象,需要传一个函数，这个函数需要满足实例化的委托的规则，没有参数、没有返回值
            MyDelegate fun = new MyDelegate(MyFunction);   // MyFunction这个函数先存到了fun这个委托变量中
            // 其它逻辑
            Console.WriteLine("abc!"); 
            Console.WriteLine("123"); 
            Console.WriteLine("..."); 

            // 调用委托
            fun.Invoke(); // 调用委托，就是调用了MyFunction这个函数         有点像其它语言的代理，c++的函数指针

            // 另一种写法
            MyDelegate fun2 = MyFunction; // 直接把函数名赋值给委托变量
            Console.WriteLine("java");
            Console.WriteLine("javascript");
            Console.WriteLine("python");
            fun2();  // 直接执行

            // 委托存函数必须要和委托的格式一致
            MyDelegate2 fun3 = new MyDelegate2(MyFunction2); // 这个函数的参数和返回值都要和委托一致
            Console.WriteLine(fun3(50)); 
            MyDelegate2 fun4 = MyFunction2;
            Console.WriteLine(fun3(100));

            // 在类中使用
            Test test = new Test(); 
            test.TestFun2(MyFunction, MyFunction2);


            // 委托可以存储多个函数
            test.AddFun(MyFunction, MyFunction2); // 传入两个函数
            test.fun();
            test.fun2(200);   // 执行会按顺序依次执行

            Console.WriteLine("***********");
            // 委托可以存储多个函数
            MyDelegate fun5 = MyFunction;
            //fun5 += MyFunction;   // 这样就添加了一个，但类型要是一致的
            //fun5();    // 执行两遍MyFunction函数
            //fun5 += MyFunctionNoArg;  // 这个函数的参数和返回值都要和委托一致
            fun5 = fun5 + MyFunctionNoArg;
            fun5();

            fun5 -= MyFunction; // 删除一个函数
            fun5();

            // 清空
            fun5 = null; // 清空委托变量

            // 系统定义的委托
            // Action  无参无返回值的委托
            Action action = MyFunction;
            // 指定返回类型的委托
            Func<string> func = MyFunction3; // MyFunction3是返回值类型为string的函数

            // Action<> 泛型 可以传多个参数的委托
            //Action<int> action1 = ;  // 1个参数
            //Action<int, string> action1 = ;  // 2个参数
            //Action<int, string, ....> action1 = ;  // 系统提供的最多16个参数

            //Func<int, string> func1 = ;  // 1个参数，返回值类型为string
            //Func<int, string, bool> func1 = ;  // 2个参数，返回值类型为bool
            //Func<int, string, bool, ...., double> func1 = ;  // 系统提供的最多16个参数，返回值类型为double


            // 事件的使用 事件于委托的区别
            TestEvent te = new TestEvent();
            te.myAction = null;
            //te.myEvent = null;   // 报错，事件不能在类外部赋值
            te.myAction = MyFunction; 
            //te.myEvent = MyFunction; // 报错
            te.myEvent += MyFunction; // 但事件可以在类外部追加
            //te.myEvent = te.myEvent + MyFunction; //  报错，赋值符号（=）是不允许的

            te.myAction(); // 调用
            //te.myEvent(); // 报错，事件不能在类外部调用
            //te.myEvent.Invoke(); // 事件不能在类外部调用
            te.TriggerEvent(); // 触发事件 只能在类内部封装


            // 使用匿名函数
            // 无参无返回
            // 这样声明匿名函数 只是在声明函数，没有调用
            Action a = delegate ()
            {
                Console.WriteLine("匿名函数");
            };
            // 真正调用它的时候，是这个委托容器什么时候调用，就什么时候执行这个匿名函数
            a(); // 调用匿名函数

            // 有参
            Action<int> a2 = delegate (int i)
            {
                Console.WriteLine("有参匿名函数的参数：" + i);
            };

            // 有返回值
            Func<int, string> a3 = delegate (int i)
            {
                return "有返回值的匿名函数的参数：" + i;
            };

            // 一般情况会作为函数参数传递 或者 作为函数返回值

            // 作为函数参数传递
            TestAnonymous ta = new TestAnonymous();
            ta.DoSomething(100, delegate ()
            {
                Console.WriteLine("匿名函数作为参数传递");
            });
            // 或者
            ta.DoSomething(100, a);  // 将之前定义好的传进去

            // 类中方法返回值是一个匿名函数
            ta.GetFun()(); // 直接调用

            // 匿名函数的缺点
            // 添加到委托或事件中后，不记录 无法单独移除
            Action action1 = delegate ()
            {
                Console.WriteLine("匿名函数1");
            };
            action1 += delegate ()
            {
                Console.WriteLine("匿名函数2");
            };
            action1();
            // 以上的匿名函数，没办法单独移除，如果要移除某个具体函数，就不要使用匿名函数了

            // lambda表达式的使用， 参照匿名函数
            // 无参无返回
            Action a4 = () =>
            {
                Console.WriteLine("lambda表达式");
            };
            a4(); // 调用
            // 有参
            Action<int> a5 = (int i) =>
            {
                Console.WriteLine("lambda表达式的参数：" + i);
            };
            // 参数类型都可以省略，参数类型和委托或事件容器一致
            // 只有一个参数时，括号可以省略
            Action<int> a7 = i =>
            {
                Console.WriteLine("lambda表达式的参数：" + i);
            };

            // 有返回值
            Func<int, string> a6 = i =>
            {
                return "lambda表达式的参数：" + i;
            };
            // 只有一个返回值时，括号和return都可以省略
            Func<int, string> a8 = i => "lambda表达式的参数：" + i;

            // 其它作为函数参数传递和返回值跟匿名函数一样
            // 缺点也跟匿名函数一样

            TestClosure testClosure = new TestClosure();
            testClosure.DoSomething(); // 闭包

        }

        // 定义一个函数 没有参数，没有返回值
        static void MyFunction()
        {
            Console.WriteLine("Hello World!");
        }
        static void MyFunctionNoArg()
        {
            Console.WriteLine("May Be!");
        }

        // 定义一个函数 有一个int参数，返回值为int
        static int MyFunction2(int a)
        {
            Console.WriteLine("Hello World!");
            return a;
        }

        static string MyFunction3()
        {
            return "c#";
        }
    }
}
